// filesys.cc 
//	Routines to manage the overall operation of the file system.
//	Implements routines to map from textual file names to files.
//
//	Each file in the file system has:
//	   A file header, stored in a sector on disk 
//		(the size of the file header data structure is arranged
//		to be precisely the size of 1 disk sector)
//	   A number of data blocks
//	   An entry in the file system directory
//
// 	The file system consists of several data structures:
//	   A bitmap of free disk sectors (cf. bitmap.h)
//	   A directory of file names and file headers
//
//      Both the bitmap and the directory are represented as normal
//	files.  Their file headers are located in specific sectors
//	(sector 0 and sector 1), so that the file system can find them 
//	on bootup.
//
//	The file system assumes that the bitmap and directory files are
//	kept "open" continuously while Nachos is running.
//
//	For those operations (such as Create, Remove) that modify the
//	directory and/or bitmap, if the operation succeeds, the changes
//	are written immediately back to disk (the two files are kept
//	open during all this time).  If the operation fails, and we have
//	modified part of the directory and/or bitmap, we simply discard
//	the changed version, without writing it back to disk.
//
// 	Our implementation at this point has the following restrictions:
//
//	   there is no synchronization for concurrent accesses
//	   files have a fixed size, set when the file is created
//	   files cannot be bigger than about 3KB in size
//	   there is no hierarchical directory structure, and only a limited
//	     number of files can be added to the system
//	   there is no attempt to make the system robust to failures
//	    (if Nachos exits in the middle of an operation that modifies
//	    the file system, it may corrupt the disk)
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.
#undef  FILESYS_STUB
#ifndef FILESYS_STUB

#include "copyright.h"
#include "debug.h"
#include "disk.h"
#include "pbitmap.h"
#include "directory.h"
#include "filehdr.h"
#include "filesys.h"

static const int TransferSize = 128;

//----------------------------------------------------------------------
// FileSystem::FileSystem
// 	Initialize the file system.  If format = TRUE, the disk has
//	nothing on it, and we need to initialize the disk to contain
//	an empty directory, and a bitmap of free sectors (with almost but
//	not all of the sectors marked as free).  
//
//	If format = FALSE, we just have to open the files
//	representing the bitmap and the directory.
//
//	"format" -- should we initialize the disk?
//----------------------------------------------------------------------

FileSystem::FileSystem(bool format) {
    DEBUG(dbgFile, "Initializing the file system.");
    if (format) {
        PersistentBitmap *freeMap = new PersistentBitmap(NumSectors);
        Directory *directory = new Directory();
        FileHeader *mapHdr = new FileHeader;
        FileHeader *dirHdr = new FileHeader;

        DEBUG(dbgFile, "Formatting the file system.");

        // First, allocate space for FileHeaders for the directory and bitmap
        // (make sure no one else grabs these!)
        freeMap->Mark(FreeMapSector);
        freeMap->Mark(DirectorySector);

        // Second, allocate space for the data blocks containing the contents
        // of the directory and bitmap files.  There better be enough space!

        ASSERT(mapHdr->Allocate(freeMap, FreeMapFileSize));
        ASSERT(dirHdr->Allocate(freeMap, DirectoryFileSize));

        // Flush the bitmap and directory FileHeaders back to disk
        // We need to do this before we can "Open" the file, since open
        // reads the file header off of disk (and currently the disk has garbage
        // on it!).

        DEBUG(dbgFile, "Writing headers back to disk.");
        mapHdr->WriteBack(FreeMapSector);
        dirHdr->WriteBack(DirectorySector);

        // add ./ and ../, the ../ in root is root its self.
        directory->Add("./", DirectorySector);
        directory->Add("../", DirectorySector);

        // OK to open the bitmap and directory files now
        // The file system operations assume these two files are left open
        // while Nachos is running.

        freeMapFile = new OpenFile(FreeMapSector);
        OpenFile *directoryFile = new OpenFile(DirectorySector);

        // Once we have the files "open", we can write the initial version
        // of each file back to disk.  The directory at this point is completely
        // empty; but the bitmap has been changed to reflect the fact that
        // sectors on the disk have been allocated for the file headers and
        // to hold the file data for the directory and bitmap.

        DEBUG(dbgFile, "Writing bitmap and directory back to disk.");
        freeMap->WriteBack(freeMapFile); // flush changes to disk
        directory->WriteBack(directoryFile);

        if (debug->IsEnabled('f')) {
            freeMap->Print();
            directory->Print();
        }

        // set current Directory to directory,
        currentDirectory = directory;
        dirStack.push(currentDirectory->Find("./"));
        strcpy(curDirPath, "/");

        delete directoryFile;
        delete freeMap;
        delete mapHdr;
        delete dirHdr;
    } else {
        // if we are not formatting the disk, just open the files representing
        // the bitmap and directory; these are left open while Nachos is running
        freeMapFile = new OpenFile(FreeMapSector);
        OpenFile *directoryFile = new OpenFile(DirectorySector);

        currentDirectory = new Directory();
        currentDirectory->FetchFrom(directoryFile);
        delete directoryFile;

        strcpy(curDirPath, "/");
    }
}

FileSystem::~FileSystem() {
    delete currentDirectory;
    delete freeMapFile;
}

void FileSystem::Name2dirname(const char name[], char dirname[]) {
    int i;
    // dirname only have one '/', dirname must less than FileNameMaxLen.
    for (i = 0; name[i] != '/' && name[i] != '\0' && i < FileNameMaxLen - 1; ++i) {
        dirname[i] = name[i];
    }
    dirname[i++] = '/';
    dirname[i++] = '\0';
}

bool FileSystem::Mkdir(const char *name) {
    char dirname[FileNameMaxLen + 1];
    Name2dirname(name, dirname);
    DEBUG(dbgFile, "Creating directory " << dirname);

    if (!Create(dirname, DirectoryFileSize)) { // Create directory file
        printf("FileSystem::Mkdir: couldn't create directory file %s\n",
                dirname);
        return false;
    }
    DEBUG(dbgFile, "created " << dirname << "....");

    OpenFile* dirFile = Open(dirname);
    if (dirFile == NULL) {
        printf("FileSystem::Mkdir: couldn't open directory file %s\n", dirname);
        return false;
    }
    Directory *dir = new Directory();
    dir->Add("./", currentDirectory->Find(dirname));
    dir->Add("../", currentDirectory->Find("./"));
    dir->WriteBack(dirFile);

    delete dirFile;
    return true;
}

bool FileSystem::Rmdir(const char *name) {
    char dirname[FileNameMaxLen + 1];
    Name2dirname(name, dirname);
    DEBUG(dbgFile, "Remove directory " << dirname);

    if (strcmp(dirname, "./") == 0) {
        cout << "please don\'t try to remove your self..." << endl;
        return false;
    }
    else if (strcmp(dirname, "../") == 0) {
        cout << "please don\'t try to remove your parent!" << endl;
        return false;
    }

    // recursive directory and remove directory one by one.
    if (!Pushd(dirname)) {
        cout << "the \"" << dirname << "\" is not found..." << endl;
        return false;
    }
    char * subdir;
    // if there are sub dirs, remove them first.
    while ((subdir = currentDirectory->GetFirstDirectory()) != NULL) {
        Rmdir(subdir);
    }

    // no subdir, remove sub files.
    char * subfile;
    while ((subfile = currentDirectory->GetFirstFile()) != NULL) {
        Remove(subfile);
    }

    // then pop and remove this dir file.
    Popd();

    cout << "Removed directory: " << dirname << endl;
    return Remove(dirname);
}

bool FileSystem::Cd(const char *name) {
    char dirname[FileNameMaxLen + 1];
    Name2dirname(name, dirname);
    DEBUG(dbgFile, "Remove directory " << dirname);

    OpenFile *dirFile = Open(dirname);
    if (dirFile == NULL) {
        printf("FileSystem::Cd: couldn't open directory file %s\n", dirname);
        return false;
    }

    DEBUG(dbgFile, "starting cd to " << dirname);
    currentDirectory->FetchFrom(dirFile);

    return true;
}

bool FileSystem::Pushd(const char *name) {
    dirStack.push(currentDirectory->Find("./"));
    if (Cd(name) == true)
        return true;
    else {
        Popd();
        return false;
    }
}

bool FileSystem::Popd() {
	if (!dirStack.empty())
	{
	    OpenFile *dirFile = new OpenFile(dirStack.top());
		currentDirectory->FetchFrom(dirFile);
		dirStack.pop();

		return true;
	}
	else
	{
		cout<<"Oh,no,directory stack is empty!!"<<endl;
		return false;
	}
}

char *FileSystem::Pwd() {
    UpdateCurPath();
    return curDirPath;
}

void FileSystem::UpdateCurPath() {
    int curFileSector = currentDirectory->Find("./");
    Pushd("../");
    // if pushd to .. is the current sector, in the root.
    if (currentDirectory->Find("./") == curFileSector) {
        curDirPath[0] = '/';
        curDirPath[1] = '\0';

        Popd();
        return;
    }

    // if not root path, continue update.
    UpdateCurPath();

    // start append path name.
    strcat(curDirPath, currentDirectory->Get(curFileSector));
    Popd();
}

//----------------------------------------------------------------------
// FileSystem::Create
// 	Create a file in the Nachos file system (similar to UNIX create).
//	Since we can't increase the size of files dynamically, we have
//	to give Create the initial size of the file.
//
//	The steps to create a file are:
//	  Make sure the file doesn't already exist
//        Allocate a sector for the file header
// 	  Allocate space on disk for the data blocks for the file
//	  Add the name to the directory
//	  Store the new file header on disk 
//	  Flush the changes to the bitmap and the directory back to disk
//
//	Return TRUE if everything goes ok, otherwise, return FALSE.
//
// 	Create fails if:
//   		file is already in directory
//	 	no free space for file header
//	 	no free entry for file in directory
//	 	no free space for data blocks for the file 
//
// 	Note that this implementation assumes there is no concurrent access
//	to the file system!
//
//	"name" -- name of file to be created
//	"initialSize" -- size of file to be created
//----------------------------------------------------------------------

bool FileSystem::Create(char *name, int initialSize) {
    PersistentBitmap *freeMap;
    FileHeader *hdr;
    int sector;
    bool success;

    DEBUG(dbgFile, "Creating file " << name << " size " << initialSize);

    if (currentDirectory->Find(name) != -1)
        success = FALSE; // file is already in directory
    else {
        freeMap = new PersistentBitmap(freeMapFile, NumSectors);
        sector = freeMap->FindAndSet(); // find a sector to hold the file header
        if (sector == -1)
            success = FALSE; // no free block for file header
        else if (!currentDirectory->Add(name, sector))
            success = FALSE; // no space in directory
        else {
            hdr = new FileHeader;
            if (!hdr->Allocate(freeMap, initialSize))
                success = FALSE; // no space on disk for data
            else {
                success = TRUE;
                // everthing worked, flush all changes back to disk
                hdr->WriteBack(sector);
                freeMap->WriteBack(freeMapFile);

                // change current directory file list.
                OpenFile *directoryFile = Open("./");
                currentDirectory->WriteBack(directoryFile);
            }
            delete hdr;
        }
        delete freeMap;
    }
    return success;
}

//----------------------------------------------------------------------
// FileSystem::Open
// 	Open a file for reading and writing.  
//	To open a file:
//	  Find the location of the file's header, using the directory 
//	  Bring the header into memory
//
//	"name" -- the text name of the file to be opened
//----------------------------------------------------------------------

OpenFile *
FileSystem::Open(char *name) {
    OpenFile *openFile = NULL;
    int sector;

    DEBUG(dbgFile, "Opening file" << name);
    sector = currentDirectory->Find(name);
    if (sector >= 0)
        openFile = new OpenFile(sector); // name was found in directory
    return openFile; // return NULL if not found
}

//----------------------------------------------------------------------
// FileSystem::Remove
// 	Delete a file from the file system.  This requires:
//	    Remove it from the directory
//	    Delete the space for its header
//	    Delete the space for its data blocks
//	    Write changes to directory, bitmap back to disk
//
//	Return TRUE if the file was deleted, FALSE if the file wasn't
//	in the file system.
//
//	"name" -- the text name of the file to be removed
//----------------------------------------------------------------------

bool FileSystem::Remove(char *name) {
    PersistentBitmap *freeMap;
    FileHeader *fileHdr;
    int sector;

    sector = currentDirectory->Find(name);
    if (sector == -1) {
        return FALSE; // file not found
    }
    fileHdr = new FileHeader;
    fileHdr->FetchFrom(sector);

    freeMap = new PersistentBitmap(freeMapFile, NumSectors);

    fileHdr->Deallocate(freeMap); // remove data blocks
    freeMap->Clear(sector); // remove header block
    currentDirectory->Remove(name);

    freeMap->WriteBack(freeMapFile); // flush to disk
    OpenFile *directoryFile = Open("./");
    currentDirectory->WriteBack(directoryFile); // flush to disk
    delete fileHdr;
    delete freeMap;
    return TRUE;
}

//----------------------------------------------------------------------
// FileSystem::ChangefileName
// 	cope a file in nachos
//----------------------------------------------------------------------
void
FileSystem::NachosfileCopy(char *from, char *to)
{
	static const int TransferSize = 128;
    OpenFile* fromFile = Open(from);
    OpenFile* toFile;
    int amountRead, fileLength;
    char *buffer;

    // Open NachOS file
    if (fromFile == NULL) {
    	printf("Copy: couldn't open input file %s\n", from);
    	return;
    }

// Figure out length of Nachos file
    fileLength = fromFile->Length();

// Create a Nachos file of the same length
    DEBUG('f', "Copying file " << from << " of size " << fileLength <<  " to file " << to);
    if (!Create(to, fileLength)) {   // Create Nachos file
        printf("Copy: couldn't create output file %s\n", to);
        delete fromFile;
        return;
    }

    toFile = Open(to);
    ASSERT(toFile != NULL);

// Copy the data in TransferSize chunks
    buffer = new char[TransferSize];
    while ((amountRead=fromFile->Read(buffer, sizeof(char)*TransferSize)) > 0)
	{
    	toFile->Write(buffer, amountRead);

	}

// Close the Nachos files
    delete toFile;
    delete fromFile;
}

void
FileSystem::AppendFileFromUnix(char *from,char *to)
{

    int fd;
    OpenFile* openFile;
    int amountRead, fileLengthbefore,fileLength,start;
    char *buffer;

// Open UNIX file
    if ((fd = OpenForReadWrite(from,FALSE)) < 0) {
        printf("Append: couldn't open input file %s\n", from);
        return;
    }

// Figure out length of UNIX file
    Lseek(fd, 0, 2);
    fileLength = Tell(fd);
    Lseek(fd, 0, 0);

    if (fileLength == 0)
	{
		printf("Append: nothing to append from file %s\n", from);
		return;
	}

    if ((openFile = Open(to)) == NULL)
    {
//        printf("Append: unable to open append file %s\n", to);
//        return;

		// file "to" does not exits, then create one
		if (!Create(to, 0))
		{
			printf("Append: couldn't create the file %s to append\n", to);
			Close(fd);
			return;
		}
		openFile = Open(to);
    }

    fileLengthbefore = openFile->Length();
    if(!(openFile->AppendFileSize(fileLength))){
         printf("The appending file is too big!\nAppending file failed\n");
         return;
    }

    start = fileLengthbefore;
    printf("start appending at %d\n",start);

    openFile->Seek(start);

   //write appended content back to the disk
   buffer = new char[TransferSize];
   while ((amountRead=ReadPartial(fd, buffer, sizeof(char)*TransferSize)) > 0)
   {
//	   openFile->Write(buffer, amountRead);
		int result;
		printf("start value: %d,  amountRead %d, ", start, amountRead);
		result = openFile->WriteAt(buffer, amountRead, start);
		printf("result of write: %d\n", result);
		start += amountRead;
   }

// Write the inode back to the disk, because we have changed it
    openFile->WriteBack(currentDirectory->Find(to));
    printf("inodes have been written back\n");
    Close(fd);

}


void
FileSystem::AppendFileFromNachos(char *from,char *to)
{

    OpenFile *openFileFrom,*openFileTo;
    int amountRead, fileLengthbefore,start;
    char *buffer;

    if ((openFileFrom = Open(from)) == NULL)
    {
    	cout<<"cannot open file"<<from<<endl;
    	return;
    }

    if ((openFileTo = Open(to)) == NULL)
    {
//        printf("Append: unable to open append file %s\n", to);
//        return;

		// file "to" does not exits, then create one
		if (!Create(to, 0))
		{
			printf("Append: couldn't create the file %s to append\n", to);
			return;
		}
		openFileTo = Open(to);
    }

    fileLengthbefore = openFileTo->Length();
    if(!(openFileTo->AppendFileSize(openFileFrom->Length()))){
         printf("The appending file is too big!\nAppending file failed\n");
         return;
    }

    start = fileLengthbefore;
    printf("start appending at %d\n",start);

    openFileTo->Seek(start);

   //write appended content back to the disk
   buffer = new char[TransferSize];
   while ((amountRead=openFileFrom->Read(buffer,TransferSize))>0)
   {
//	   openFile->Write(buffer, amountRead);
		int result;
		printf("start value: %d,  amountRead %d, ", start, amountRead);
		result = openFileTo->WriteAt(buffer, amountRead, start);
		printf("result of write: %d\n", result);
		start += amountRead;
   }

// Write the inode back to the disk, because we have changed it
    openFileTo->WriteBack(currentDirectory->Find(to));
}

void
FileSystem::PrintFile(char *filename)
{

    OpenFile *openFile;
    int i, amountRead;
    char *buffer;

    if ((openFile = Open(filename)) == NULL) {
        printf("Print: unable to open file %s\n", filename);
        return;
    }

    buffer = new char[TransferSize];
    while ((amountRead = openFile->Read(buffer, TransferSize)) > 0)
        for (i = 0; i < amountRead; i++)
            printf("%c", buffer[i]);
    delete [] buffer;

    delete openFile;            // close the Nachos file
    return;

}


void
FileSystem::CopyFileFromUnix(char *from,char *to)
{

    int fd;
    OpenFile* openFile;
    int amountRead, fileLength;
    char *buffer;

// Open UNIX file
    if ((fd = OpenForReadWrite(from,FALSE)) < 0) {
        printf("Copy: couldn't open input file %s\n", from);
        return;
    }

// Figure out length of UNIX file
    Lseek(fd, 0, 2);
    fileLength = Tell(fd);
    Lseek(fd, 0, 0);

// Create a Nachos file of the same length
    DEBUG('f', "Copying file " << from << " of size " << fileLength <<  " to file " << to);
    if (!this->Create(to, fileLength)) {   // Create Nachos file
        printf("Copy: couldn't create output file %s\n", to);
        Close(fd);
        return;
    }

    openFile = Open(to);
    ASSERT(openFile != NULL);

// Copy the data in TransferSize chunks
    buffer = new char[TransferSize];
    while ((amountRead=ReadPartial(fd, buffer, sizeof(char)*TransferSize)) > 0)
	{
    	openFile->Write(buffer, amountRead);

	}

// Close the UNIX and the Nachos files
    delete openFile;
    Close(fd);

}

//----------------------------------------------------------------------
// FileSystem::ChangefileName
// 	remove the file name.
//----------------------------------------------------------------------
void
FileSystem::ChangefileName(char *name1, char *name2)
{
	currentDirectory->modifyfileName(name1, name2);
	OpenFile* file = Open("./");
	currentDirectory->WriteBack(file);        // flush to disk
	delete file;
}


//----------------------------------------------------------------------
// FileSystem::List
// 	List all the files in the file system directory.
//----------------------------------------------------------------------
void FileSystem::List(char *dirname) {
    if (dirname == NULL)
    {
		dirname = "./";
		Cd(dirname);
		currentDirectory->List();
    }
    else
    {
    	Cd(dirname);
		currentDirectory->List();
		Cd("../");
    }

}

//----------------------------------------------------------------------
// FileSystem::Print
// 	Print everything about the file system:
//	  the contents of the bitmap
//	  the contents of the directory
//	  for each file in the directory,
//	      the contents of the file header
//	      the data in the file
//----------------------------------------------------------------------

void FileSystem::Print() {
    FileHeader *bitHdr = new FileHeader;
    FileHeader *dirHdr = new FileHeader;
    PersistentBitmap *freeMap = new PersistentBitmap(freeMapFile, NumSectors);

    printf("Bit map file header:\n");
    bitHdr->FetchFrom(FreeMapSector);
    bitHdr->Print();

    printf("Directory file header:\n");
    dirHdr->FetchFrom(DirectorySector);
    dirHdr->Print();

    freeMap->Print();

    currentDirectory->Print();

    delete bitHdr;
    delete dirHdr;
    delete freeMap;
}

void FileSystem::Selftest() {
    char dirname[5] = "dir0";
    for (int i = 0; i < 8; i++) {
        dirname[3] = '0' + i;
        Mkdir(dirname);
        List();
        Cd(dirname);
    }
    for (int i = 7; i >= 0; i--) {
        Cd("../");
        dirname[3] = '0' + i;
        Rmdir(dirname);
        List();
    }
}

#endif // FILESYS_STUB
